require "prefabutil"
local math = math
local db = TUNING.MOD_ShadowFieldExciter
local prefab_id = db.Prefab_ID

local assets =
{
    --Asset("ALTAS", "images/inventoryimages/"..db.Prefab_ID..".xml"), -- 物品图标的图集文件
    Asset("ANIM", "anim/"..db.Prefab_ID..".zip"),  -- ShadowFieldExciter的动画文件(放在modmain里面一起加载了)
}

local prefabs =
{
    'collapse_small', --小型坍塌效果
    'shadowhandsfx', --暗影手特效
}


local FindItemsRadius = db.FindItemsRadius --寻物半径
local LightRadius = db.LightRadius --光照半径
local PLACER_SCALE = FindItemsRadius * 2 / 12.48 --放置器的缩放比例
local PREFAB_SCALE = 0.8 --预制物的动画缩放比例
local PickInterval = db.PickInterval --寻找物品的间隔时间
local HarvestCookpot = db.HarvestCookpot --是否收集烹饪锅内的物品
local CURRENT_DATA_VERSION = 1.1


---------------------------------工具函数----------------------------------------
--确认物品是不是护符(靠名字来判断)
local function CheckItemIsAmulet(item) -- 定义一个本地函数 CheckItemIsAmulet，接收一个参数 item
    return string.find(item.prefab, "[aA][mM][uU][lL][eE][tT]") ~= nil -- 使用 string.find 检查 item 的 prefab 名称是否包含 "amulet"（不区分大小写），如果找到则返回 true，否则返回 false
end

--得到当前的动画名称
local function GetCurrentAnimationName(inst) -- 定义一个本地函数 GetCurrentAnimationName，接收一个参数 inst
    local debug_str = inst:GetDebugString() -- 获取实例 inst 的调试字符串
    local anim_str = debug_str:match("anim: ([%w_]+)") -- 从调试字符串中匹配 "anim: " 后面的动画名称（由字母、数字、下划线组成）
    return anim_str -- 返回匹配到的动画名称
end

--对被捕获的正在燃烧的item进行灭火
local function Extinguish(item) -- 定义一个本地函数 Extinguish，接收一个参数 item
    if item.components and item.components.burnable then  -- 检查物品是否有 burnable 组件（可燃烧）
        if item.components.burnable:IsBurning() then  -- 检查物品是否正在燃烧或正在冒烟（即将燃烧）
            item.components.burnable:Extinguish() -- 调用 burnable 组件的 Extinguish 方法灭火
        end
        
    end
end
-------------------------------------寻找函数-------------------------------------------
--确认物品是否可以放进这个容器
local function CheckContainerForItem(container,item) -- 定义一个本地函数 CheckContainerForItem，接收两个参数 container 和 item
    if container.components.container:CanTakeItemInSlot(item) -- 检查容器的 container 组件是否可以在某个槽位接受该物品
    and not container.components.stewer -- 并且该容器没有 stewer 组件（不是烹饪锅）
    and not container.components.inventoryitem -- 并且该容器没有 inventoryitem 组件（不是可携带的容器，如背包）
    then
        return true -- 如果以上条件都满足，返回 true
    end
    return false -- 否则返回 false
end

--#region 检查物品是否在列表中的辅助函数
local function IsItemInList(item_prefab, list)
    for _, prefab in ipairs(list) do -- 遍历列表 list
        if prefab == item_prefab then -- 如果列表中的某个元素等于 item_prefab
            return true -- 返回 true
        end
    end
    return false -- 如果遍历完列表都没有找到匹配的元素，返回 false
end

--#endregion

--#region 用来测试的函数
local function CheckNil(inst)
    if inst ~= nil then -- 如果 inst 不为 nil
        return '_'..tostring(true)..'_' -- 返回包含 "true" 的字符串
    end
    return '_'..tostring(false)..'_' -- 否则返回包含 "false" 的字符串
end
--#endregion

--#region 确保容器有效性
local function CheckContainerValid(container)
    if container and container.components -- 检查容器是否存在且有 components 表
    and container.components.container then -- 并且容器本身有 container 组件
        return true -- 如果所有条件都满足，返回 true，表示容器有效
    end
    return false -- 如果有任何条件不满足，返回 false
end
--#endregion

----------------------------

--#region 寻找自身一定半径内的在地上的物品和烹饪锅,返回一个物品表、一个烹饪锅表
local function FindItemsAndCookpotOnGround(inst, radius)
    local pos = inst:GetPosition() -- 获取实例 inst 的位置

    local items = TheSim:FindEntities(pos.x, pos.y, pos.z, radius, {}, {'fx','decor','inlimbo'}) -- 在指定位置和半径内查找实体，排除带有 'fx', 'decor', 'inlimbo' 标签的实体
    --print(unpack(items)) -- 调试输出找到的物品列表
    local targetItems = {} -- 初始化一个空表，用于存储符合条件的物品
    local targetCookpots = {}

    -- 因为单机版燃烧物品的inventoryitem组件会被暂时移除，所以灭火得放在这里提前进行,开启的dlc的话就灭火，没有dlc没法灭火后还原inventoryitem组件
    if db.ROG_DLC or db.CAPY_DLC or db.HAM_DLC then
        for _, item in ipairs(items) do
            if item.components and item.components.burnable then -- 检查物品是否有 burnable 组件（可燃烧）
                Extinguish(item) -- 调用 Extinguish 函数对物品进行灭火处理
            end
        end
    end
    -- 对找到的物品进行遍历和筛选
    for _, item in ipairs(items) do -- 遍历找到的实体
    
        --#region 找寻烹饪完成的烹饪锅
        if HarvestCookpot and item.components 
        and item.components.stewer
        and item.components.stewer.product
        and item.components.stewer.product ~= 'wetgoop'
        and item.components.stewer.done
        then -- 检查物品是否有 stewer 组件（烹饪锅）并且是烹饪完成的状态
            table.insert(targetCookpots, item) -- 如果是烹饪锅且烹饪完成，将其添加到 targetCookpots 表中
        end
        --#endregion

        if item.components.inventoryitem -- 检查物品是否有 inventoryitem 组件
            and item.components.inventoryitem.canbepickedup -- 并且该物品可以被拾取
            and item.components.inventoryitem.cangoincontainer -- 并且该物品可以放入容器
            and item:GetPosition().y < 0.0001 -- 并且该物品在地面上（Y坐标接近0）
        then
            --print("找到物品"..tostring(item)) -- 调试输出找到的物品
            -- 检查白名单
            if IsItemInList(item.prefab, inst.ItemWhiteList) then -- 如果物品的 prefab 在实例的白名单 (ItemWhiteList) 中
                table.insert(targetItems, item) -- 将该物品添加到 targetItems 表中
            -- 检查黑名单
            elseif not IsItemInList(item.prefab, inst.ItemBlackList) then -- 如果物品的 prefab 不在实例的黑名单 (ItemBlackList) 中
                -- 检查收集规则
                local ignore = (inst.CollectRule.IgnoreWeapon and item.components.weapon) -- 如果设置了忽略武器规则且物品是武器
                            or (inst.CollectRule.IgnoreArmor and item.components.armor) -- 或者设置了忽略护甲规则且物品是护甲
                            or (inst.CollectRule.IgnoreAmulet and CheckItemIsAmulet(item)) -- 或者设置了忽略护符规则且物品是护符
                            or (inst.CollectRule.IgnoreUnStackable and not item.components.stackable) -- 或者设置了忽略不可堆叠规则且物品不可堆叠
                            or (inst.CollectRule.IgnoreFood and item.components.edible) -- 或者设置了忽略食物规则且物品是食物
                if not ignore then -- 如果以上忽略条件都不满足
                    table.insert(targetItems, item) -- 将该物品添加到 targetItems 表中
                end
            end
        end
    end
    return targetItems,targetCookpots -- 返回找到的物品列表和烹饪锅列表
end
--#endregion

--#region 寻找自身一定半径内的容器,返回一个容器表
local function FindContainersInRadius(inst,radius)
    local pos = inst:GetPosition() -- 获取实例的位置
    local containers = TheSim:FindEntities(pos.x,pos.y,pos.z,radius,{},{'fx','decor','inlimbo'}) -- 在指定位置和半径内查找实体，排除带有 'fx', 'decor', 'inlimbo' 标签的实体
    local targetContainers = {} -- 初始化一个空表，用于存储找到的容器
    for key, container in ipairs(containers) do -- 遍历找到的实体
        if container.prefab ~= db.Prefab_ID and container.components and container.components.container then -- 检查实体是否不是自身预制件，并且有 components 表和 container 组件
            table.insert(targetContainers,container) -- 将符合条件的容器添加到 targetContainers 表中
        end
    end
    return targetContainers -- 返回包含所有符合条件容器的表
end
--#endregion

----------------------------

--#region 在寻找范围内为item选择合适的容器,返回一个容器对象
local function SelectContainerForItem(inst,item,containers)
    if item ~= nil and item:IsValid() then -- 检查物品是否存在且有效
        local targetContainers = {} -- 初始化一个空表，用于存储初步筛选后的有效容器
        --初步筛选有效容器
        for _, container in ipairs(containers) do -- 遍历传入的容器列表
            -- 检查容器是否能容纳该物品，并且满足匹配模式规则（如果开启）或容器中已有同类物品
            if CheckContainerForItem(container,item) and (not inst.CollectMode.MatchMode or container.components.container:Has(item.prefab,1)) then
                table.insert(targetContainers,container) -- 将符合条件的容器添加到 targetContainers 表中
            end
        end

        --按照优先级选择容器
        --第一优先级,选择已经有相同物品的容器
        for _, container in ipairs(targetContainers) do -- 遍历初步筛选后的容器列表
            if container.components.container:Has(item.prefab,1) then -- 如果容器中已经存在至少一个同类物品
                for i = 1,container.components.container.numslots  do -- 遍历容器的所有槽位
                    local slot_item = container.components.container.slots[i] -- 获取当前槽位的物品
                    if not slot_item or -- 如果槽位为空
                    (slot_item.components.stackable ~= nil -- 或者槽位物品可堆叠
                    and container.components.container.acceptsstacks -- 并且容器接受堆叠
                    and slot_item.prefab == item.prefab -- 并且槽位物品与目标物品是同一种类
                    and slot_item.skinname == item.skinname -- 并且皮肤相同
                    and not slot_item.components.stackable:IsFull()) -- 并且该堆叠未满
                    then
                        return container -- 返回这个容器作为目标容器
                    end
                end
            end
        end

        --第二优先级，对有保质期的物品选择能延长保质期的容器
        if item.components.perishable then -- 如果物品有 perishable 组件（会腐烂）
            local localPerishMultiplyer = 1 -- 初始化本地腐烂倍率，默认为 1（正常速度）
            local targetContainer -- 初始化目标容器变量
            local tmp -- 初始化临时变量，用于存储容器的腐烂倍率

            for _, container in ipairs(targetContainers) do -- 遍历初步筛选后的容器列表
                if not container.components.container:IsFull() -- 如果容器未满
                and item.prefab ~= "spoiled_food" -- 并且物品不是腐烂食物
                and item.prefab ~= "rottenegg" -- 并且物品不是腐烂的蛋
                and item.prefab ~= "spoiled_fish" -- 并且物品不是腐烂的鱼
                and item.prefab ~= "wetgoop" -- 并且物品不是潮湿黏糊
                then
                    tmp = 2 -- 设置一个较高的默认腐烂倍率（大部分容器不会超过这个值）
                    
                    if container:HasTag("fridge") then -- 如果容器有 "fridge" 标签（是冰箱）
                        tmp = TUNING.PERISH_FRIDGE_MULT -- 使用冰箱的标准保鲜系数
                    end

                    if tmp < localPerishMultiplyer then -- 如果当前容器的腐烂倍率低于记录的最低倍率
                        localPerishMultiplyer = tmp -- 更新最低腐烂倍率
                        targetContainer = container -- 将当前容器设为目标容器
                    end
                end
            end
            if targetContainer ~=nil then -- 如果通过腐烂倍率找到了最佳容器
                return targetContainer -- 返回该容器
            end
        end

        --第三优先级 选择未满容器
        for _, container in ipairs(targetContainers) do -- 再次遍历初步筛选后的容器列表
            if not container.components.container:IsFull() then -- 如果容器未满
                return container -- 返回这个未满的容器
            end
        end
    end
    -- 如果没有找到合适的容器，则隐式返回 nil
end
--#endregion

--#region 定义一个本地函数 T_MovementOnY_axis，用于在单独的线程中处理物品在 Y 轴上的动画移动（物体在Y轴上进行移动的动画部分）,on_complete_cb: 动画完成时调用的回调函数
local function T_MovementOnY_axis(item, container, on_complete_cb)
    local start_time = GetTime() -- 记录动画开始时间
    local x, _, z = item.Transform:GetWorldPosition() -- 获取物品的 X 和 Z 坐标，Y 坐标将被动画控制
    
    while item:IsValid() do -- 循环直到物品失效
        local elapsed = GetTime() - start_time -- 计算经过的时间
        local y = 0 -- 初始化 Y 坐标
        
        -- 阶段1: 0 - 0.16 秒，物品保持在地面 (y=0)
        if elapsed < 0.16 then
            y = 0 -- Y 坐标为 0
            
        -- 阶段2: 0.16 - 0.2 秒 (持续 0.04 秒)，物品从 0 快速上升到 1.5，使用二次加速曲线
        elseif elapsed < 0.2 then
            local t = (elapsed - 0.16) / 0.04 -- 计算当前阶段的时间进度 (0 到 1)
            y = 1.5 * t * t  -- 应用二次加速插值计算 Y 坐标
        
        -- 阶段3: 0.2 - 0.63 秒 (持续 0.43 秒)，物品从 1.5 缓慢上升到 2.0，使用余弦缓动曲线
        elseif elapsed < 0.63 then
            local t = (elapsed - 0.2) / 0.43 -- 计算当前阶段的时间进度 (0 到 1)
            y = 1.5 + 0.5 * (1 - math.cos(t * math.pi))  -- 应用余弦缓动插值计算 Y 坐标
        
        -- 阶段4: 0.63 - 0.73 秒 (持续 0.1 秒)，物品从 2.0 快速下降到 0.2，使用二次加速曲线
        elseif elapsed < 0.73 then
            local t = (elapsed - 0.63) / 0.1 -- 计算当前阶段的时间进度 (0 到 1)
            y = 2.0 - 1.8 * t * t  -- 应用二次加速插值计算 Y 坐标（下降）
        
        -- 阶段5: 动画完成
        else
            if on_complete_cb then -- 如果提供了回调函数
                on_complete_cb(item, container) -- 调用回调函数，传入物品和容器
            end
            break -- 退出循环
        end
        
        item.Transform:SetPosition(x, y, z) -- 更新物品的位置
        Sleep(FRAMES * GetTickTime()) -- 暂停一帧，让出控制权，使动画平滑进行
    end
end
--#endregion

--#region 传送物品到目标容器的具体操作函数（传送部分）
local function TransferItemsIntoContainers(item,container)
    if item:IsValid() then -- 检查物品是否有效（未被移除或销毁）
        if CheckContainerValid(container) then -- (DS 修改) 调用修改后的检查函数
            container.components.container:GiveItem(item) -- 调用容器的 GiveItem 方法将物品放入容器
            item:RemoveTag("NOCLICK") -- 移除物品的 "NOCLICK" 标签，使其可以再次被玩家点击交互
            --print(tostring(item).."允许点击") -- 调试输出
            item.Physics:SetActive(true) -- 重新激活物品的物理引擎
        else -- 如果容器或其副本无效
            item:RemoveTag("NOCLICK") -- 同样移除 "NOCLICK" 标签
            --print(tostring(item).."允许点击") -- 调试输出
            item.Physics:SetActive(true) -- 重新激活物品的物理引擎
            local pos = item:GetPosition() -- 获取物品当前位置
            item.components.inventoryitem:DoDropPhysics(pos.x,0,pos.z) -- 让物品执行掉落物理效果，使其回到地面
        end
    else -- 如果物品本身无效
        return -- 直接返回，不做任何操作
    end
end
--#endregion

--#region 将目标物品放进目标容器里面的整体方法（播放动画+Y轴移动→传送物品到容器）
local function PutItemInContainer(item,container)
    --在物品周围生成动画特效
    local fx = SpawnPrefab("shadowhandsfx")  -- 生成名为 "shadowhandsfx" 的预制件（暗影之手特效）
    local x,y,z = item.Transform:GetWorldPosition()  -- 获取物品当前的位置
    fx.Transform:SetPosition(x,y,z) -- 将特效的位置设置到物品的位置
    ------------------
    --在动画播放过程种用代码匹配物品高度，让视觉上看起来就像是物品被托高了一样
    -- 启动运动线程，传入完成回调
    item:StartThread(function() 
        T_MovementOnY_axis(item, container, TransferItemsIntoContainers)
    end)
end

--#endregion

----------------------------

--#region 为烹饪锅的料理物品选择合适的容器
local function SelectContainerForCookpotProduct(product,containers)
    -- 第一优先级，选择已经有相同物品的容器
    for _, container in pairs(containers) do
        if container.components.container:Has(product,1) then -- 如果容器中已经存在至少一个同类物品
            for i = 1,container.components.container.numslots  do -- 遍历容器的所有槽位
                local slot_item = container.components.container.slots[i] -- 获取当前槽位的物品
                if not slot_item or -- 如果槽位为空
                (slot_item.components.stackable ~= nil -- 或者槽位物品可堆叠
                and container.components.container.acceptsstacks -- 并且容器接受堆叠
                and slot_item.prefab == product -- 并且槽位物品与目标物品是同一种类
                and not slot_item.components.stackable:IsFull()) -- 并且该堆叠未满
                then
                    return container -- 返回这个容器作为目标容器
                end
            end
        end
    end

    -- 第二优先级，对有保质期的物品选择能延长保质期的容器
    local localPerishMultiplyer = 1 -- 初始化本地腐烂倍率，默认为 1（正常速度）
    local targetContainer = nil -- 初始化目标容器变量
    local tmp -- 初始化临时变量，用于存储容器的腐烂倍率
    for _, container in pairs(containers) do
        if not container.components.container:IsFull() then
            tmp = 2 -- 设置一个较高的默认腐烂倍率（大部分容器不会超过这个值）
            
            if container:HasTag("fridge") then -- 如果容器有 "fridge" 标签（是冰箱）
                tmp = TUNING.PERISH_FRIDGE_MULT -- 使用冰箱的标准保鲜系数
            end

            if tmp < localPerishMultiplyer then -- 如果当前容器的腐烂倍率低于记录的最低倍率
                localPerishMultiplyer = tmp -- 更新最低腐烂倍率
                targetContainer = container -- 将当前容器设为目标容器
            end
        end
    end
    if targetContainer ~=nil then -- 如果通过腐烂倍率找到了最佳容器
        return targetContainer -- 返回该容器
    end
end

--#endregion

--#region 尝试将烹饪锅的成品放入容器的具体操作（传送部分）
local function HarvestStewerToContainer(cookpot,container)
    
    local stewer = cookpot.components.stewer
	if stewer.done then
	    if stewer.onharvest then
		    stewer.onharvest(stewer.inst)
	    end
	    stewer.done = nil
	    if stewer.product then 
		    if container and container.components.container then
		    	local loot = SpawnPrefab(stewer.product)

		    	if loot then
                    if loot.components.inventoryitem then			
		    			if loot and loot.components.perishable then
		    			loot.components.perishable:SetPercent( stewer.product_spoilage)
		    			end
		    			container.components.container:GiveItem(loot) -- 将烹饪完成的产品放入容器中
				    end
			    end
			    stewer.product = nil
		    end
		
		    if stewer.inst.components.container then		
			    stewer.inst.components.container.canbeopened = true
		    end
		
		    return true
	    end
    end
end

--#endregion

--#region 尝试将烹饪锅的成品放入容器的整体操作：播放动画再进行具体操作
local function PutCookpotProductInContainer(cookpot,container)
    cookpot:StartThread(function()
        --在烹饪锅生成动画特效
        local fx = SpawnPrefab("shadowhandsfx") -- 生成名为 "shadowhandsfx" 的预制件（暗影之手特效）
        local x,y,z = cookpot.Transform:GetWorldPosition() -- 获取烹饪锅当前的位置
        fx.Transform:SetPosition(x,y,z) -- 将特效的位置设置到烹饪锅的位置
        Sleep(0.3) -- 暂停0.3秒，等待特效播放完毕
        HarvestStewerToContainer(cookpot,container) -- 调用 HarvestStewerToContainer 函数将烹饪锅的产品放入目标容器
    end)
end

--#endregion

--#region 定义一个本地函数 FindItemsRegularly，用于定期检测并处理周围的物品
local function FindItemsRegularly(inst)
    if inst:HasTag("burnt") then -- 如果实例（机器）有 "burnt" 标签（已被烧毁）
        return -- 直接返回，不执行任何操作
    end

    if inst.components.machine.ison then -- 如果机器的 machine 组件状态为 on（已开启）
        local items,cookpots = FindItemsAndCookpotOnGround(inst,FindItemsRadius) -- 调用 FindItemsOnGround 函数查找指定半径内的地面物品

        --print(unpack(items)) -- 调试输出找到的物品

        --地上物品拾取部分
        if next(items) ~= nil then -- 如果找到了至少一个物品 (items 表不为空)
            local containers = FindContainersInRadius(inst,FindItemsRadius) -- 调用 FindContainersInRadius 函数查找指定半径内的容器
            if next(containers) ~= nil then -- 如果找到了至少一个容器 (containers 表不为空)
                for _, item in pairs(items) do -- 遍历找到的物品列表
                    local targetContainer = SelectContainerForItem(inst,item, containers) -- 为当前物品选择一个合适的目标容器
                    if targetContainer ~= nil and not item:HasTag("NOCLICK") then -- 如果找到了合适的目标容器且物品没有 "NOCLICK" 标签（表示未被处理）
                        item:AddTag("NOCLICK") -- 给物品添加 "NOCLICK" 标签，防止重复处理或玩家交互
                        item.Physics:SetActive(false)  -- 禁用物品的物理效果
                        PutItemInContainer(item,targetContainer) -- 调用 PutItemInContainer 函数将物品放入目标容器（包含动画）
                        break  -- 处理完第一个符合条件的物品后，退出循环，避免一次处理多个物品
                    end
                end
            end
        end
        -- 烹饪锅完成的料理拾取部分
        if next(cookpots) ~= nil then -- 如果找到了至少一个烹饪完成的烹饪锅 (cookpots 表不为空)
            local containers = FindContainersInRadius(inst,FindItemsRadius) -- 调用 FindContainersInRadius 函数查找指定半径内的容器
            if next(containers) ~= nil then
                for _, cookpot in pairs(cookpots) do
                    if cookpot.components.stewer
                    and cookpot.components.stewer.product
                    and cookpot.components.stewer.done
                    then
                        local targetContainer = SelectContainerForCookpotProduct(cookpot.components.stewer.product, containers) -- 为烹饪锅的产品选择一个合适的目标容器
                        if targetContainer ~= nil then
                            PutCookpotProductInContainer(cookpot,targetContainer) -- 调用 HarvestStewerToContainer 函数将烹饪锅的产品放入目标容器
                            break -- 处理完第一个符合条件的烹饪锅后，退出循环，避免一次处理多个烹饪锅
                        end
                    end
                end
            end
        end

    end
end

--#endregion
-------------------------------------回调函数-------------------------------------------
--建造完成回调函数
local function OnBuilt(inst)
    --inst.SoundEmitter:PlaySound("dontstarve_DLC001/common/firesupressor_craft") -- 播放建造音效（被注释掉）
    inst.AnimState:PlayAnimation('place') -- 播放名为 'place' 的放置动画
    inst.AnimState:PushAnimation('idle_off') -- 在放置动画播放完毕后，紧接着播放 'idle_off'（关闭状态的闲置）动画
end

 --机器开机函数
local function TurnOn(inst)
    --print("开启机器时容器开启状态"..tostring(inst.components.container:IsOpen())) -- 调试输出：打印机器容器当前的打开状态
    if not inst:HasTag('burnt') then --and not inst.components.container:IsOpen() then -- 检查机器是否未被烧毁，并且其容器组件当前是关闭状态
        inst.components.machine.ison = true -- 设置机器的 machine 组件状态为 on
        if not GetClock():IsNight() then -- 检查当前是否是白天 (不是夜晚)
            inst.AnimState:PlayAnimation('turn_on_daytime',false) -- 播放 'turn_on_daytime'（白天开启）动画，false 表示不循环
            inst.AnimState:PushAnimation('idle_on_daytime',true) -- 接着播放 'idle_on_daytime'（白天开启闲置）动画，true 表示循环播放
            inst.Light:Enable(false) -- 禁用灯光效果，因为是白天
        else -- 如果当前是夜晚
            inst.AnimState:PlayAnimation('turn_on_night',false) -- 播放 'turn_on_night'（夜晚开启）动画
            inst.AnimState:PushAnimation('idle_on_night',true) -- 接着播放 'idle_on_night'（夜晚开启闲置）动画
            inst.Light:Enable(true) -- 启用灯光效果
        end
        -- 只有在非休眠状态下才启动定时任务
        if not inst:IsAsleep() then -- 检查实例是否处于休眠状态（例如，在洞穴中而玩家在地面）
            if inst.FindItemsTask == nil then -- 如果定时查找物品的任务 (FindItemsTask) 不存在
                -- 创建一个新的周期性任务：每隔 PickInterval 秒执行一次 FindItemsRegularly 函数，初始延迟为 1 秒
                inst.FindItemsTask = inst:DoPeriodicTask(PickInterval, FindItemsRegularly, 1)
            end
        end
    end
end

--机器关机函数
local function TurnOff(inst)
    if not inst:HasTag('burnt') then -- 检查机器是否未被烧毁
        inst.components.machine.ison = false -- 设置机器的 machine 组件状态为 off
        inst.Light:Enable(false) -- 禁用灯光效果
        local currentAnim = GetCurrentAnimationName(inst) -- 获取机器当前正在播放的动画名称
        if currentAnim == 'idle_on_daytime' or currentAnim == 'turn_on_daytime' then -- 如果当前是白天的开启或闲置动画
            inst.AnimState:PlayAnimation('turn_off_daytime',false) -- 播放 'turn_off_daytime'（白天关闭）动画
            inst.AnimState:PushAnimation('idle_off',true) -- 接着播放 'idle_off'（关闭闲置）动画
        elseif currentAnim == 'idle_on_night' or currentAnim == 'turn_on_night' then -- 如果当前是夜晚的开启或闲置动画
            inst.AnimState:PlayAnimation('turn_off_night',false) -- 播放 'turn_off_night'（夜晚关闭）动画
            inst.AnimState:PushAnimation('idle_off',true) -- 接着播放 'idle_off'（关闭闲置）动画
        end

        if inst.FindItemsTask ~=nil then -- 如果定时查找物品的任务 (FindItemsTask) 存在
            inst.FindItemsTask:Cancel() -- 取消该任务
            inst.FindItemsTask = nil -- 将任务变量置为 nil
            --print('理论上到这里就已经删除定时任务了') -- 调试输出
        end
    end
end

-- 定义一个本地函数 OnHammered，当实例被锤子或类似工具/攻击摧毁时调用
local function OnHammered(inst,worker)
    -- 检查实例是否有 burnable 组件并且正在燃烧
    if inst.components.burnable ~= nil and inst.components.burnable:IsBurning() then
        inst.components.burnable:Extinguish() -- 如果正在燃烧，则灭火
    end

    inst.components.lootdropper:DropLoot() -- 调用 lootdropper 组件掉落预设的物品
    -- 生成小型坍塌特效
    local fx = SpawnPrefab("collapse_small") -- 生成名为 "collapse_small" 的预制件
    fx.Transform:SetPosition(inst.Transform:GetWorldPosition()) -- 将特效的位置设置到实例当前的位置
    --fx:SetMaterial("metal") -- 设置特效的材质为 "metal" DS无此设置
    inst:Remove() -- 从游戏中移除该实例
end

-- 定义一个本地函数 OnHit，当实例被攻击或锤击（但未被摧毁）时调用
local function OnHit(inst,worker)
    if not inst:HasTag('burnt') then -- 检查实例是否没有 "burnt" 标签（未被烧毁）
        inst.AnimState:PlayAnimation('hit') -- 播放名为 'hit' 的受击动画
        -- 根据机器当前的运行状态，在受击动画后恢复到相应的闲置动画
        if inst.components.machine.ison then -- 如果机器当前是开启状态
            if not GetClock():IsNight() then -- 如果当前是白天
                inst.AnimState:PushAnimation('turn_on_daytime',false)
                inst.AnimState:PushAnimation('idle_on_daytime',true) -- 紧接着播放 'idle_on_daytime'（白天开启闲置）动画
            else -- 如果当前是夜晚
                inst.AnimState:PushAnimation('turn_on_night',false)
                inst.AnimState:PushAnimation('idle_on_night',true) -- 紧接着播放 'idle_on_night'（夜晚开启闲置）动画
            end
        else -- 如果机器当前是关闭状态
            inst.AnimState:PushAnimation('idle_off',true) -- 紧接着播放 'idle_off'（关闭闲置）动画
        end
    end
end

-- 数据验证函数：确保传入的是一个 table，否则返回一个空 table
local function ValidateList(list)
    return type(list) == "table" and list or {} -- 如果 list 的类型是 "table"，则返回 list 本身，否则返回一个新的空表 {}
end

-- 浅拷贝工具函数：创建一个 table 的浅拷贝
local function shallowCopy(orig)
    local copy = {} -- 创建一个新的空 table 'copy'
    for k, v in pairs(orig) do -- 遍历原始 table 中的所有键值对
        copy[k] = v -- 将键值对复制到新的 table 中
    end
    return copy -- 返回新的浅拷贝 table
end

-- 定义 OnSave 函数，用于在游戏保存时序列化实例数据
local function OnSave(inst, data)
    data.DataVersion = CURRENT_DATA_VERSION -- 在保存数据中记录当前的数据版本号
    data.ItemWhiteList = shallowCopy(inst.ItemWhiteList) -- 浅拷贝实例的白名单列表并存入 data
    data.ItemBlackList = shallowCopy(inst.ItemBlackList) -- 浅拷贝实例的黑名单列表并存入 data
    data.CollectRule = shallowCopy(inst.CollectRule) -- 浅拷贝实例的收集规则表并存入 data
    data.CollectMode = shallowCopy(inst.CollectMode) -- 浅拷贝实例的收集模式表并存入 data
    return data -- 返回包含要保存数据的 table
end

-- 定义 OnLoad 函数，用于在游戏加载时恢复实例数据
local function OnLoad(inst, data)
    if data then -- 检查是否存在有效的加载数据
        -- 版本迁移逻辑（示例）
        if data.DataVersion ~= CURRENT_DATA_VERSION then -- 如果加载的数据版本与当前代码版本不符
            -- 这里可以添加代码来处理旧版本数据到新版本的转换
            print("Data version mismatch! Expected:", CURRENT_DATA_VERSION, "Got:", data.DataVersion) -- 提示版本不匹配
            if data.DataVersion == 1 then
                data.CollectMode = {
                    MatchMode = (data.CollectRule and data.CollectRule.MatchMode) or true, -- 如果 CollectRule 中有 MatchMode，则使用它，否则默认为 true
                }

                if data.CollectRule then
                    data.CollectRule.MatchMode = nil -- 清除旧版本中的 MatchMode
                end
            end
        end
        -- 加载名单数据
        inst.ItemWhiteList = ValidateList(data.ItemWhiteList) -- 加载白名单，并使用 ValidateList 确保其为 table
        inst.ItemBlackList = ValidateList(data.ItemBlackList) -- 加载黑名单，并使用 ValidateList 确保其为 table
        
        -- 合并配置规则
        if data.CollectRule then -- 如果加载数据中包含收集规则
            for k, v in pairs(data.CollectRule) do -- 遍历加载的规则
                if data.CollectRule[k] ~= nil then -- 确保加载的规则值不为 nil (避免覆盖为 nil)
                    inst.CollectRule[k] = data.CollectRule[k] -- 将加载的规则值更新到实例的 CollectRule 表中
                end
            end
        end
        if data.CollectMode then
            for k, v in pairs(data.CollectMode) do -- 遍历加载的收集模式
                if data.CollectMode[k] ~= nil then -- 确保加载的模式值不为 nil (避免覆盖为 nil)
                    inst.CollectMode[k] = data.CollectMode[k] -- 将加载的模式值更新到实例的 CollectMode 表中
                end
            end
        end
        
    end
end

-- 定义 OnOpen 函数，在容器被打开时调用
local function OnOpen(inst)
    inst.isOn_OnOpen = inst.components.machine:IsOn() -- 记录打开容器时机器是否处于开启状态，存入临时变量 isOn_OnOpen
    print("开启容器时机器当前状态"..tostring(inst.isOn_OnOpen)) -- 调试输出：打印打开容器时的机器状态
    if inst.isOn_OnOpen then -- 如果打开容器时机器是开启的
        inst.components.machine.turnofffn(inst) -- 调用机器组件的关闭函数 (turnofffn) 来关闭机器
    end
end

-- 定义 OnClose 函数，在容器被关闭时调用
local function OnClose(inst)
    --print("[DEBUG] OnClose called for", inst) -- 调试输出：表明 OnClose 被调用
    --print("[DEBUG] Container state:", inst.components.container:IsOpen()) -- 调试输出：打印容器关闭后的状态
    --print("[DEBUG] Machine state:", inst.isOn_OnOpen) -- 调试输出：打印之前记录的机器状态
    
    if inst.isOn_OnOpen then -- 如果在打开容器时机器是开启的
        -- 使用 DoTaskInTime 添加一个短暂的延迟 (0.1 秒)
        -- 这是为了确保容器状态完全更新后再检查，避免竞态条件
        inst:DoTaskInTime(0.1, function()
            if not inst.components.container:IsOpen() then -- 再次确认容器确实是关闭状态
                inst.components.machine.turnonfn(inst) -- 调用机器组件的开启函数 (turnonfn) 来重新开启机器
            end
        end)
    end
end

-- 定义 OnEntitySleep 函数，在实体进入休眠状态时调用
local function OnEntitySleep(inst)
    if inst.FindItemsTask ~= nil then -- 如果定时查找物品的任务存在
        inst.FindItemsTask:Cancel() -- 取消该任务
        inst.FindItemsTask = nil -- 将任务变量置为 nil
    end
end

-- 定义 OnEntityWake 函数，在实体从休眠状态唤醒时调用
local function OnEntityWake(inst)
    -- 如果机器的 machine 组件状态为 on (应该处于开启状态)
    if inst.components.machine.ison then
        if inst.FindItemsTask == nil then -- 并且定时查找物品的任务当前不存在
            -- 重新创建并启动周期性任务
            inst.FindItemsTask = inst:DoPeriodicTask(PickInterval, FindItemsRegularly, 1)
        end
    end
end

-- 定义 OnPhaseChanged 函数，在世界时间阶段改变时 (白天/黄昏/夜晚) 调用
local function OnPhaseChanged(inst,phase)
    
    if not inst.components.machine.ison then return end  -- 如果机器当前未开启，则直接返回，不执行任何操作

    -- 取消之前可能存在的与阶段变化相关的延迟任务 (例如延迟关闭灯光)
    if inst._phaselighttask ~= nil then -- 检查是否存在延迟任务句柄
        inst._phaselighttask:Cancel() -- 取消任务
        inst._phaselighttask = nil -- 清除任务句柄
    end

    if phase == "night" then -- 如果新的阶段是夜晚
        local currentAnim = GetCurrentAnimationName(inst) -- 获取当前动画名称
        if currentAnim == "idle_on_daytime" then -- 如果当前是白天闲置动画
            -- 平滑过渡到夜晚动画：先播放白天关闭，再播放夜晚开启，最后循环夜晚闲置
            inst.AnimState:PushAnimation('turn_off_daytime')
            inst.AnimState:PushAnimation('turn_on_night')
            inst.AnimState:PushAnimation('idle_on_night',true)
        end
        inst.Light:Enable(true)  -- 启用灯光效果
    else -- 如果新的阶段不是夜晚 (即白天或黄昏)
        local currentAnim = GetCurrentAnimationName(inst) -- 获取当前动画名称
        if currentAnim == "idle_on_night" then -- 如果当前是夜晚闲置动画
            -- 平滑过渡到白天动画：先播放夜晚关闭，再播放白天开启，最后循环白天闲置
            inst.AnimState:PushAnimation('turn_off_night')
            inst.AnimState:PushAnimation('turn_on_daytime')
            inst.AnimState:PushAnimation('idle_on_daytime',true)
        end
        -- 创建一个延迟任务，在 5 秒后关闭灯光
        inst._phaselighttask = inst:DoTaskInTime(5, function()
            inst.Light:Enable(false)  -- 禁用灯光效果
            inst._phaselighttask = nil  -- 任务执行完毕后清除句柄
        end)
    end
end
------------------------------------放置实体函数-----------------------------------------
-- 定义 OnEnableHelper 函数，用于启用或禁用放置时的视觉辅助圈
local function OnEnableHelper(inst, enabled)
    if enabled then -- 如果要启用辅助圈
        if inst.helper == nil then -- 如果辅助圈实体还未创建
            inst.helper = CreateEntity() -- 创建一个新的实体作为辅助圈

            --[[ 配置辅助圈实体为非网络同步实体 ]]
            inst.helper.entity:SetCanSleep(false) -- 设置为不能休眠
            inst.helper.persists = false -- 设置为不持久化（不保存到存档）

            inst.helper.entity:AddTransform()  -- 添加 Transform 组件，用于位置、旋转、缩放
            inst.helper.entity:AddAnimState()  -- 添加 AnimState 组件，用于动画控制

            inst.helper:AddTag("CLASSIFIED")  -- 添加 "CLASSIFIED" 标签，通常用于客户端可见但服务器不关心的实体
            inst.helper:AddTag("NOCLICK")  -- 添加 "NOCLICK" 标签，使其不能被鼠标点击
            inst.helper:AddTag("placer")  -- 添加 "placer" 标签，标记其为放置辅助物

            -- 根据预设的 PLACER_SCALE 计算并设置辅助圈的缩放比例 (X, Y, Z)
            inst.helper.Transform:SetScale(math.sqrt(PLACER_SCALE) , math.sqrt(PLACER_SCALE), math.sqrt(PLACER_SCALE))


            inst.helper.AnimState:SetBank(db.Prefab_ID)  -- 设置动画状态机的 Bank（动画资源库）
            inst.helper.AnimState:SetBuild(db.Prefab_ID)  -- 设置动画状态机的 Build（动画构建定义）
            inst.helper.AnimState:PlayAnimation("placer")  -- 播放名为 "placer" 的动画（通常是范围指示圈）
            inst.helper.AnimState:SetLightOverride(1)  -- 设置光照覆盖值为 1，使其不受环境光影响，保持明亮
            inst.helper.AnimState:SetOrientation(ANIM_ORIENTATION.OnGround)  -- 设置动画朝向为贴合地面
            inst.helper.AnimState:SetLayer(LAYER_BACKGROUND)  -- 设置渲染层级为背景层，避免遮挡其他重要物体
            inst.helper.AnimState:SetSortOrder(1)  -- 设置排序顺序为 1
            inst.helper.AnimState:SetAddColour(0, .2, .5, 0)  -- 设置附加颜色（R, G, B, A），给辅助圈一个特定的颜色叠加效果 (这里是淡蓝色)

            inst.helper.entity:SetParent(inst.entity)  -- 将辅助圈实体的父级设置为当前实例实体，使其跟随移动

        end
    elseif inst.helper ~=nil then -- 如果要禁用辅助圈，并且辅助圈实体存在
        inst.helper:Remove() -- 从游戏中移除辅助圈实体
        inst.helper = nil -- 将实例的 helper 变量置为 nil
    end
end

--#region 容器槽位设置
local slotpos = {} -- 定义一个空表 slotpos，用于存储槽位位置
for y = 0, 5, 1 do
    table.insert(slotpos, Vector3(-501, 188 + y*(-67.5), 0))
    table.insert(slotpos, Vector3(-444, 154 + y*(-67.5), 0))
end

--#endregion

-- 生成预制物实体的主函数 (Prefab Constructor)
local function fn(Sim)
    
    -- 创建新实体基础框架
    local inst = CreateEntity()  -- 创建一个新的实体实例
    -- 添加基础组件系统
    inst.entity:AddTransform()       -- 添加 Transform 组件，用于处理位置、旋转和缩放
    inst.entity:AddAnimState()       -- 添加 AnimState 组件，用于处理动画播放和状态
    inst.entity:AddSoundEmitter()    -- 添加 SoundEmitter 组件，用于播放音效
    --inst.entity:AddMiniMapEntity()   -- 添加 MiniMapEntity 组件，用于在小地图上显示图标
    
    inst.entity:AddLight()           -- 添加 Light 组件，使实体能发光
    inst.Light:SetIntensity(0.8)       -- 设置光照强度为 0.8
    inst.Light:SetRadius(LightRadius) -- 设置光照半径 (从 db.LightRadius 获取)
    inst.Light:SetColour(1,1,1)  -- 设置光照颜色为白色 (R=1, G=1, B=1)
    inst.Light:SetFalloff(0.8)        -- 设置光照衰减率为 0.8
    inst.Light:Enable(false)           -- 默认禁用光照，将在 TurnOn 函数中根据需要启用

    -- 设置小地图属性 (被注释掉的代码)
    -- inst.MiniMapEntity:SetPriority(5)          -- 设置小地图图标的显示优先级
    -- inst.MiniMapEntity:SetIcon(db.Prefab_ID..".tex")  -- 设置小地图图标使用的纹理文件


    -- 初始化动画系统
    inst.AnimState:SetBank(db.Prefab_ID)       -- 设置动画状态机使用的 Bank 名称 (从 db.Prefab_ID 获取)
    inst.AnimState:SetBuild(db.Prefab_ID)      -- 设置动画状态机使用的 Build 名称 (从 db.Prefab_ID 获取)
    inst.AnimState:SetScale(PREFAB_SCALE, PREFAB_SCALE, PREFAB_SCALE)  -- 设置动画的缩放比例 (从 PREFAB_SCALE 获取)
    inst.AnimState:PlayAnimation("idle_off",true)    -- 默认播放 "idle_off" (关闭闲置) 动画，并循环播放 (true)

    MakeObstaclePhysics(inst, 0.25)  --- 创建一个障碍物物理组件，设置半径为 0.5。
    inst:AddTag("structure")         -- 给实体添加 "structure" 标签，表示它是一个建筑


    --#region (注释区域开始) 当玩家用鼠标点击物品准备移动或丢弃时显示放置器,但是属于非必要功能，而且可能有bug，暂不启用
        -- 下面的代码块被注释掉了，其目的是尝试监听玩家选择物品的事件，并据此显示或隐藏放置辅助圈
        -- local function TryListenToPlayer()
        --     if ThePlayer then -- 检查本地玩家是否存在
        --         -- 监听本地玩家的 "newactiveitem" 事件 (当玩家拿起或放下物品时触发)
        --         ThePlayer:ListenForEvent("newactiveitem", function(player, data)
        --             if data and data.item then -- 如果事件数据中包含有效的物品
        --                 if inst.helper == nil then -- 如果辅助圈不存在
        --                     inst.components.deployhelper.onenablehelper(inst,true) -- 启用辅助圈
        --                     print("找到了物品") -- 调试输出
        --                 else -- 如果辅助圈已存在 (理论上不应发生，但作为保险)
        --                     inst.helper:Remove() -- 移除现有辅助圈
        --                     inst.helper = nil -- 清空变量
        --                 end
                        
        --             else -- 如果事件数据中没有物品 (玩家放下了物品或选择了空手)
        --                 if inst.helper ~= nil then -- 如果辅助圈存在
        --                     inst.helper:Remove() -- 移除辅助圈
        --                     inst.helper = nil -- 清空变量
        --                 end
        --                 print("没找到物品") -- 调试输出
        --             end
        --         end)
        --         return true -- 表示成功设置监听器
        --     end
        --     return false -- 表示本地玩家不存在，无法设置监听器
        -- end

        -- -- 如果第一次尝试设置监听器失败 (例如玩家实体尚未完全加载)
        -- if not TryListenToPlayer() then
        --     -- 创建一个周期性任务，每 0.1 秒尝试一次设置监听器
        --     inst.playerCheckTask = inst:DoPeriodicTask(0.1, function()
        --         if TryListenToPlayer() then -- 如果尝试成功
        --             -- 取消这个周期性检查任务
        --             if inst.playerCheckTask then
        --                 inst.playerCheckTask:Cancel()
        --                 inst.playerCheckTask = nil
        --             end
        --         end
        --     end)
        --#endregion (注释区域结束)
    

    -- 初始化白名单 (本地)
    inst.ItemWhiteList = {}

    -- 初始化黑名单 (本地)
    inst.ItemBlackList = {}

    -- 初始化收集规则 (本地)
    inst.CollectRule = {
        IgnoreWeapon = false,
        IgnoreArmor = false,
        IgnoreAmulet = false,
        IgnoreUnStackable = false,
        IgnoreFood = false,
        IgnoreSomething = false,
    }
    inst.CollectMode = {
        MatchMode = true,  -- 默认启用匹配模式
    }

    --#region (区域开始) 事件监听
    -- 注册 "onbuilt" 事件监听器，当建筑完成时调用 OnBuilt 函数
    inst:ListenForEvent("onbuilt",OnBuilt)
    
    --注册时间变化事件监听器
    inst:ListenForEvent("daytime", function()
        OnPhaseChanged(inst,"daytime")
    end,GetWorld())
    inst:ListenForEvent("dusktime", function()
        OnPhaseChanged(inst,"dusk")
    end,GetWorld())
    inst:ListenForEvent("nighttime", function()
        OnPhaseChanged(inst,"night")
    end,GetWorld())
    --#endregion (区域结束)

    -- 添加可检查组件 (Inspectable)
    inst:AddComponent("inspectable")  -- 使玩家可以检查该实体 (默认显示名称和描述)


    --#region 添加机器组件 (Machine)
    inst:AddComponent("machine")  -- 用于管理实体的开关状态和相关逻辑
    inst.components.machine.turnonfn = TurnOn  -- 将 TurnOn 函数设置为机器开启时的回调
    inst.components.machine.turnofffn = TurnOff  -- 将 TurnOff 函数设置为机器关闭时的回调
    --inst.components.machine.caninteractfn = CanInteract  -- 设置一个函数来决定玩家是否可以与机器交互 (被注释掉)
    inst.components.machine.cooldowntime = 1.6  -- 设置开启/关闭操作之间的冷却时间为 1.6 秒，防止玩家快速切换
    inst.isOn_OnOpen = inst.components.machine:IsOn()

    --#endregion

    inst:AddComponent("lootdropper")

    --#region -- 添加 Workable 组件，使实体可以被玩家或其他实体进行“工作”交互
    inst:AddComponent("workable")  
    inst.components.workable:SetWorkAction(ACTIONS.HAMMER)  -- 设置需要使用锤子 (HAMMER) 才能进行工作
    inst.components.workable:SetWorkLeft(4)  -- 设置需要工作 4 次才能完成 (例如锤击 4 次摧毁)
    inst.components.workable:SetOnFinishCallback(OnHammered)  -- 将 OnHammered 函数设置为工作完成时的回调 (被锤毁)
    inst.components.workable:SetOnWorkCallback(OnHit)  -- 将 OnHit 函数设置为每次工作进行时的回调 (被锤击)
    
    --#endregion

    --#region 添加容器组件 (Container)
    inst:AddComponent("container")
    
    inst.components.container:SetNumSlots(#slotpos)  -- 设置容器的槽位数量为 slotpos 表的长度 (12个槽位)
    inst.components.container.onopenfn = OnOpen
    inst.components.container.onclosefn = OnClose
    inst.components.container.widgetpos = Vector3(0, 0, 0)
    inst.components.container.widgetslotpos = slotpos
    inst.components.container.side_align_tip = 0
    inst.components.container.widgetanimbank = "sfe_interface"
    inst.components.container.widgetanimbuild = "sfe_interface"

    --#endregion

    -- 绑定各种生命周期事件的回调函数
    inst.OnSave = OnSave  -- 游戏保存时调用 OnSave 函数
    inst.OnLoad = OnLoad  -- 游戏加载时调用 OnLoad 函数
    inst.OnEntitySleep = OnEntitySleep  -- 实体进入休眠时调用 OnEntitySleep 函数
    inst.OnEntityWake = OnEntityWake  -- 实体唤醒时调用 OnEntityWake 函数

    
    return inst -- 返回最终配置好的实体实例
end

-- 定义 Placer_Postinit_fn 函数，用于在创建放置指示物 (placer) 后进行额外的初始化,这个函数创建的是跟随鼠标移动的建筑虚影，而不是地上的范围圈
local function Placer_Postinit_fn(inst)
    local placer2 = CreateEntity()  -- 创建一个新的实体，作为建筑虚影
    placer2.entity:SetCanSleep(false)  -- 禁止休眠
    placer2.persists = false  -- 不持久化

    placer2.entity:AddTransform()  -- 添加 Transform 组件
    placer2.entity:AddAnimState()  -- 添加 AnimState 组件

    placer2:AddTag("NOCLICK")  -- 添加 "NOCLICK" 标签
    placer2:AddComponent("placer")

    -- 计算缩放比例的倒数，因为这个虚影需要抵消放置器本身的缩放
    local s = 1/math.sqrt(PLACER_SCALE)
    placer2.Transform:SetScale(s, s, s) -- 设置虚影的缩放
    -- 设置虚影动画本身的缩放，使其与最终建成的建筑大小一致
    placer2.AnimState:SetScale(PREFAB_SCALE, PREFAB_SCALE, PREFAB_SCALE)

    placer2.AnimState:SetBank(db.Prefab_ID)  -- 设置动画 Bank
    placer2.AnimState:SetBuild(db.Prefab_ID)  -- 设置动画 Build
    placer2.AnimState:PlayAnimation("idle_off",false)  -- 播放关闭状态的闲置动画作为虚影外观，不循环
    placer2.AnimState:SetLightOverride(1)  -- 设置光照覆盖，使其看起来清晰

    placer2.entity:SetParent(inst.entity) -- 将虚影实体的父级设置为放置指示物实体，使其跟随移动

    inst.components.placer:LinkEntity(placer2)  -- 将创建的虚影实体链接到放置指示物的 placer 组件
end

-- 定义 MakePlacerForDLC 函数，用于创建放置指示物 (placer) 的预制件
local function MakePlacerForDLC()
    if db.CAPY_DLC or db.HAM_DLC then
        return     
        MakePlacer(db.Prefab_ID.."_placer",             -- 放置指示物的名称 (例如 "shadowfieldexciter_placer")
        db.Prefab_ID,                    -- 放置指示物动画 Bank
        db.Prefab_ID,                    -- 放置指示物动画 Build
        "placer_point",                                     -- 默认播放的动画 (范围圈)
        true,                                       
        nil, nil,                                   -- 点击测试偏移 (未使用)
        math.sqrt(PLACER_SCALE),                               -- 范围圈的缩放比例
        nil, nil,nil,false,false,nil,                                   -- 点击测试尺寸 (未使用)
        nil)                        -- 放置指示物创建后的回调函数 (添加建筑虚影)
    else
        return
        MakePlacer(db.Prefab_ID.."_placer",             -- 放置指示物的名称 (例如 "shadowfieldexciter_placer")
        db.Prefab_ID,                    -- 放置指示物动画 Bank
        db.Prefab_ID,                    -- 放置指示物动画 Build
        "placer",                                     -- 默认播放的动画 (范围圈)
        true,                                       
        nil, nil,                                   -- 点击测试偏移 (未使用)
        math.sqrt(PLACER_SCALE),                               -- 范围圈的缩放比例
        nil, nil,                                   -- 点击测试尺寸 (未使用)
        Placer_Postinit_fn)                       -- 放置指示物创建后的回调函数 (添加建筑虚影)
    end
end

return
    Prefab(prefab_id, fn, assets, prefabs),
    MakePlacerForDLC()
